using System;
using System.Reflection;
using System.Text;

namespace MonoMobile.Dialogs.Remote.Discovery
{
    public static class SerializerHelpers  
    {  
        public static byte[] Serialize(object o)  
        {  
            // Create a maximally- sized buffer for serializing into  
            byte[] buffer = new byte[MAX_SERIALIZED_SIZE];  
            int bytesWritten = 0;  
            
            FieldInfo[] fieldInfos = o.GetType().GetFields(
                BindingFlags.Instance |  BindingFlags.Public | BindingFlags.NonPublic);  
            
            foreach (FieldInfo fieldInfo in fieldInfos)  
            {  
                if (fieldInfo.FieldType == typeof(string))  
                {  
                    string theString = (string)fieldInfo.GetValue(o);  
                    bytesWritten += FromString(theString, buffer, bytesWritten);  
                }  
                else if (fieldInfo.FieldType == typeof(int))  
                {  
                    int theInt = (int)fieldInfo.GetValue(o);  
                    FromInt(theInt, buffer, bytesWritten);  
                    bytesWritten += sizeof(int);  
                }
                else if (fieldInfo.FieldType == typeof(bool))
                {
                    bool theBool = (bool)fieldInfo.GetValue(o);
                    FromBool(theBool, buffer, bytesWritten);
                    bytesWritten += sizeof(byte);
                }
                else 
                    throw new Exception("Unsupported type for serialization.");  
            }  
            
            // Create an exact-sized buffer for return  
            byte[] output = new byte[bytesWritten];  
            for (int i = 0; i < bytesWritten; i++)  
                output[i] = buffer[i];  
            
            return output;  
        }  
        
        public static object Deserialize(byte[] buffer, Type t)  
        {  
            int bytesRead = 0;  
            object o = t.GetConstructor(new Type[0]).Invoke(null);  
            
            FieldInfo[] fieldInfos = t.GetFields(
                BindingFlags.Instance |  BindingFlags.Public | BindingFlags.NonPublic);  
            
            foreach (FieldInfo fieldInfo in fieldInfos)  
            {  
                if (fieldInfo.FieldType == typeof(string))  
                {  
                    string value;  
                    bytesRead += ToString(buffer, bytesRead, out value);  
                    fieldInfo.SetValue(o, value);  
                }  
                else if (fieldInfo.FieldType == typeof(int))  
                {  
                    int value = ToInt(buffer, bytesRead);  
                    bytesRead += sizeof(int);  
                    fieldInfo.SetValue(o, value);  
                }
                else if (fieldInfo.FieldType == typeof(bool))
                {
                    bool value = ToBool(buffer, bytesRead);
                    bytesRead += sizeof(byte);
                    fieldInfo.SetValue(o, value);
                }
                else throw new Exception("Unsupported type for deserialization.");  
            }  
            
            return o;  
        }  
        
        //---  

        static int FromString(string theString, byte[] buffer, int offset)  
        {  
            byte[] tmpBuffer = Encoding.UTF8.GetBytes(theString);  
            
            // Prefix string with int length  
            FromInt(tmpBuffer.Length, buffer, offset);  
            
            // Copy to output buffer  
            int i = offset + sizeof(int);  
            foreach (byte b in tmpBuffer)  
                buffer[i++] = b;  
            return i - offset;  
        }  
        
        static int ToString(byte[] buffer, int offset, out string theString)  
        {  
            // Strings are prefixed with an integer size  
            int len = ToInt(buffer, offset);  
            
            // Encoding's GetChars() converts an entire buffer, 
            // so extract just the string part  
            byte[] tmpBuffer = new byte[len];  
            int dst = offset + sizeof(int);  
            for (int i = 0; i < len; i++, dst++)  
                tmpBuffer[i] = buffer[dst];  
            theString = new string(Encoding.UTF8.GetChars(tmpBuffer));  
            return dst - offset;  
        }  
        
        static void FromInt(int theInt, byte[] buffer, int offset)  
        {  
            buffer[offset] = unchecked((byte)(theInt));  
            buffer[offset + 1] = unchecked((byte)(theInt >> 8));  
            buffer[offset + 2] = unchecked((byte)(theInt >> 16));  
            buffer[offset + 3] = unchecked((byte)(theInt >> 24));  
        }  
        
        static int ToInt(byte[] buffer, int offset)  
        {  
            return (int)((buffer[offset] & 0x000000FF) 
                | (buffer[offset + 1] << 8 & 0x0000FF00) 
                | (buffer[offset + 2] << 16 & 0x00FF0000) 
                | (buffer[offset + 3] << 24));  
        }

        static void FromBool(bool theBool, byte[] buffer, int offset)
        {
            buffer[offset] = theBool ? (byte)1 : (byte)0;
        }

        static bool ToBool(byte[] buffer, int offset)
        {
            return buffer[offset] == (byte)0;
        }

        const int MAX_SERIALIZED_SIZE = 1024;
    } 
}
